import {
  cadAppLog,
  CMEVENTS,
  DocumentObject,
  ExtensionID,
  ShapeChangePendingEvent,
  Types,
} from '@amcax/base';
import { Picker3D, PickerMgr, Renderer3D, RendererMgr } from '@amcax/renderer';
import { measureRenderManager } from '@app-base/manager/MeasureRenderManager';
import { cmdMgr } from '@app-cad/manager/CmdManager';
import { referenceRenderManager } from '@app-cad/manager/ReferenceRenderManager';
import {
  CoreBasicCommands,
  ctx2,
  dataManager,
  docMgr,
  referenceManager,
  userEvents,
} from '@app-cad/module';
import {
  COMMON_EVENTS,
  LOADING_EVENTS,
  SHAPE_CHANGE_PENDING_EVENT_HANDLED,
} from '../Constants';
import { markManager } from '../managers/MarkManager';
import { superTask } from '../utils/superTask';
import { getEventDispater } from './dispatcher';

export interface CoreEvent {
  event: string;
  data;
}

let coreEventNeverHandled = true;

export const FileImportedEvent = 'part-file-just-imported';
let partFileJustImported = false;

export function registerImportedFlagEvent() {
  userEvents.on(FileImportedEvent, () => {
    partFileJustImported = true;
  });
}

export async function handleCoreEvent(event: ShapeChangePendingEvent) {
  await docMgr.updateDocumentObjects();
  await markManager.updateMarks();

  // 如果设计树中有材质id不存在，则也更新材质
  const materialIds = new Set<number>();
  docMgr.flatDocumentObjects.forEach((documentObject) => {
    documentObject.materials?.faces?.forEach((m) => materialIds.add(m.mat));
  });
  if (
    Array.from(materialIds).some((m) => dataManager.getMaterials(m) == null)
  ) {
    await docMgr.updateDocumentMaterials();
  }
  await cmdMgr.updateUndoSize();
  // 先更新全部
  const {
    added = [],
    removed = [],
    shapeChanged = [],
    placementChanged = [],
    propertyChanged = [],
    visibilityChanged = [],
  } = event;

  // if added, change coreEventNeverHandled only after fitObjectView is called,
  // other cases, change coreEventNeverHandled immediately
  if (
    removed.length ||
    shapeChanged.length ||
    placementChanged.length ||
    propertyChanged.length ||
    visibilityChanged.length
  ) {
    coreEventNeverHandled = false;
  }

  const renderer = RendererMgr.getRender<Renderer3D>('3d');
  const picker = PickerMgr.getPicker<Picker3D>('3d');
  // 隐藏模型
  const validVisibilityChanged = visibilityChanged.filter(
    (v) => v !== docMgr.getCurrentSketchDxid(),
  );
  if (validVisibilityChanged.length > 0) {
    cadAppLog('visibilityChanged', validVisibilityChanged);

    // emulate clickNull event for the design tree to clear the selection
    userEvents.emit(COMMON_EVENTS.CLICK_NULL);

    // 对于需要显示的对象，需要向远程请求
    const shapesToFetch: string[] = [];

    // 递归遍历整个DocumentObject树
    const processVisibilityChangedObjects = (
      objects: DocumentObject[],
      parentChanged = false,
    ) => {
      objects.forEach((documentObject) => {
        const { shape, isRecursiveVisible, dxid, children } = documentObject;
        // 父元素可见且自己的visibility为true，才说明实际上可见

        const hasChanged =
          parentChanged || validVisibilityChanged.includes(dxid);
        if (hasChanged) {
          // not used in assembly environment, no partKey
          const shapeData = dataManager.getShapeData(shape);
          if (
            shapeData != null &&
            Object.values(renderer.getModelByDxid(dxid)).filter(Boolean)
              .length > 0 &&
            !isRecursiveVisible
          ) {
            renderer.setModelVisible(dxid, isRecursiveVisible);

            if (!isRecursiveVisible) {
              picker.clearSelectionByDxid(dxid);
            }
          } else if (dataManager.isReferenceObject(documentObject)) {
            const target = referenceManager.getObjectById(dxid);

            if (target == null) {
              renderer.renderReferenceObject(
                { ...documentObject, children: [] },
                dxid,
              );
            } else if (isRecursiveVisible) {
              target.show();
            } else {
              target.hide();
              picker.clearSelectionByDxid(dxid);
            }
          } else if (documentObject.type === Types.MeasureObject) {
            measureRenderManager.setVisibility(
              documentObject.dxid,
              isRecursiveVisible,
            );
          } else if (shape && isRecursiveVisible) {
            shapesToFetch.push(dxid);

            // don't deal with children if the object is visible
            return;
          }
        }

        if (children != null) {
          processVisibilityChangedObjects(children, hasChanged);
        }
      });
    };

    processVisibilityChangedObjects(docMgr.documentObjects);

    if (shapesToFetch.length > 0) {
      try {
        if (shapesToFetch.length > reqCount * 2) {
          loadEmit(true);
        }

        await renderModel(shapesToFetch);
      } finally {
        loadEmit(false);
      }
    }

    ctx2.viewer.requestRender();
    ctx2.viewer.sceneSetup.updatePixelBuffer();
    userEvents.emit(SHAPE_CHANGE_PENDING_EVENT_HANDLED);
  }
  // 加载新模型
  if (added.length > 0) {
    let firstTimeAdd = false;
    if (added.length > reqCount * 2) {
      loadEmit(true);
    }

    const addedObjects = added.map((dxid) =>
      docMgr.getDocumentObjectById(dxid),
    );

    if (
      ctx2.cadScene.pointGroup.children.length === 0 &&
      ctx2.cadScene.edgeGroup.children.length === 0 &&
      addedObjects.some((o) => o.type !== Types.Sketcher)
    ) {
      firstTimeAdd = true;
    }

    const topLevelDxids = collectDxidsFromObjects(addedObjects);
    renderModel(topLevelDxids).finally(() => {
      if (added.includes(docMgr.getCurrentSketchDxid())) {
        renderer.setModelVisible(docMgr.getCurrentSketchDxid(), false);
      }
      ctx2.viewer.sceneSetup.updatePixelBuffer();
      if ((firstTimeAdd && coreEventNeverHandled) || partFileJustImported) {
        setTimeout(() => {
          ctx2.viewer.fitObjectView(400);
        }, 400);
        coreEventNeverHandled = false;
        partFileJustImported = false;
      }

      complete(added, CMEVENTS.MODEL_ADDED);
    });
  }

  // adjust camera for current sketch
  const currentSketchDxid = docMgr.getCurrentSketchDxid();
  if (
    currentSketchDxid &&
    [...propertyChanged, ...shapeChanged].includes(currentSketchDxid)
  ) {
    const modelsData = await CoreBasicCommands.getVisualData(
      [currentSketchDxid],
      ExtensionID.CAD,
      false,
      true,
    );
    ctx2.viewer.sceneSetup.adjustCameraForVisualData(modelsData);
  }

  // 模型变化
  const validShapeChanged = Array.from(
    new Set(
      [...propertyChanged, ...shapeChanged].filter(
        (v) => v !== currentSketchDxid,
      ),
    ),
  );

  if (validShapeChanged.length > 0) {
    cadAppLog('shapeChanged', validShapeChanged);
    if (validShapeChanged.length > reqCount * 2) {
      loadEmit(true);
    }

    const topLevelDxids = collectDxidsFromObjects(
      validShapeChanged.map((dxid) => docMgr.getDocumentObjectById(dxid)),
    );
    renderModel(topLevelDxids, {
      checkVisibility: false,
    }).finally(() => {
      complete(validShapeChanged, CMEVENTS.MODEL_SHAPE_CHANGED, true);
    });
  }

  if (propertyChanged.length > 0) {
    cadAppLog('propertychange', propertyChanged);
    userEvents.emit(SHAPE_CHANGE_PENDING_EVENT_HANDLED);
  }

  // 位置变化
  if (placementChanged.length > 0) {
    cadAppLog('placementChanged', placementChanged);
    await renderer.drawNewModels(placementChanged);
    picker.clearPickedObjects();
    userEvents.emit(CMEVENTS.MODEL_PLACEMENT_CHANGED, placementChanged);
  }
  // 删除模型
  if (removed.length > 0) {
    if (removed.includes(docMgr.getCurrentSketchDxid())) {
      // 直接import会导致运行一些单例的初始化代码，产生崩溃
      const { exitSketcherMethod } = await import('../utils');
      await exitSketcherMethod(docMgr.getCurrentSketchDxid());
      docMgr.updateDocumentObjects();
    }
    cadAppLog('removed', removed);
    picker.deleteModels(removed, true);
    referenceRenderManager.removeReferenceObjects(removed);
    measureRenderManager.removeMeasureResult(removed);
    await removed.forEach((dxid) => renderer.deleteModelByDxid(dxid));
    ctx2.viewer.sceneSetup.updatePixelBuffer();
    userEvents.emit(CMEVENTS.MODEL_REMOVED, removed);
    if (removed.includes(docMgr.workingObjectDxid)) {
      await docMgr.resetDocumentObject();
    }
    // TODO picker.deleteXXX重构后删除
    picker.clickNull();
    ctx2.viewer.requestRender();
    userEvents.emit(SHAPE_CHANGE_PENDING_EVENT_HANDLED);
  }
  // 螺纹渲染
  await renderThread(event);
}

export function filterOutDescendants(
  objects: DocumentObject[],
): DocumentObject[] {
  // Collect all descendant dxids of objects in the list
  const descendantDxids = new Set<string>();
  function collectDescendants(obj: DocumentObject) {
    if (!obj.children || obj.children.length === 0) {
      return;
    }
    for (const child of obj.children) {
      descendantDxids.add(child.dxid);
      collectDescendants(child);
    }
  }

  const validObjects = objects.filter(Boolean);
  for (const obj of validObjects) {
    collectDescendants(obj);
  }

  return validObjects.filter((obj) => !descendantDxids.has(obj.dxid));
}

export function collectDxidsFromObjects(
  objects: DocumentObject[],
  removeDescendant = true,
): string[] {
  const dxids: string[] = [];

  function traverse(node: any) {
    if (node.shape) {
      dxids.push(node.shape);
    } else if (node.children && node.children.length > 0) {
      node.children.forEach(traverse);
    }
  }

  if (removeDescendant) {
    filterOutDescendants(objects).forEach(traverse);
  } else {
    objects.forEach(traverse);
  }

  return dxids;
}

const groupSimulation = getEventDispater().group('simulationchangepending');
groupSimulation.useHook(async () => {
  docMgr.updateDocumentObjects();
});
// 完成

function complete(added: string[], event: CMEVENTS, isUpdate = false) {
  ctx2.viewer.requestRender();

  if (isUpdate) {
    referenceManager.removeObjects(added);
  }
  referenceRenderManager.renderReferenceObjects(added);
  setTimeout(() => {
    ctx2.viewer.sceneSetup.updatePixelBuffer();
  });

  userEvents.emit(event, added);
  loadEmit(false);
  loadProgressEmit(-1);
}

export const loadEmit = (value: boolean) => {
  userEvents.emit(LOADING_EVENTS.LOADING_CHANGE, value);
};

export const loadProgressEmit = (progress: number) => {
  userEvents.emit(LOADING_EVENTS.LOADING_PROGRESS, progress);
};

const reqCount = 10;

export interface ModelTaskOptions {
  force?: boolean;
  checkVisibility?: boolean;
}

export interface RenderModelOptions extends ModelTaskOptions {
  needFitObjectView?: boolean;
}

export const renderModel = (
  dxids: string[],
  {
    needFitObjectView = false,
    force = false,
    checkVisibility = true,
  }: RenderModelOptions = {},
) => {
  const filteredDxids = dxids.filter((d) => {
    const target = docMgr.getDocumentObjectById(d);

    return target != null && !dataManager.isReferenceObject(target);
  });

  const objectsCount: number = filteredDxids.length;

  (window as any)?.electronCenter?.emit(CMEVENTS.MODELS_RENDERER_START);
  loadProgressEmit(0);
  loadEmit(true);

  return new Promise((resolve) => {
    if (filteredDxids.length <= 0) {
      resolve(true);
    } else {
      let progress = -1;
      for (let i = 0, len = filteredDxids.length; i < len; i += reqCount) {
        superTask()
          .add(() =>
            modelTask(filteredDxids.slice(i, i + reqCount), {
              force,
              checkVisibility,
            }),
          )
          .then(() => {
            progress = Math.max(i / objectsCount, progress);
            loadProgressEmit(progress * 100);
            if (i <= reqCount * 4 && i > reqCount * 3 && needFitObjectView) {
              ctx2.viewer.fitObjectView(400);
            }
            if (i >= len - 1 - reqCount) {
              // 更新undo/redo数量
              if (needFitObjectView) {
                ctx2.viewer.fitObjectView(400);
              }
              resolve(true);
            }
          });
      }
    }
  }).then(() => {
    userEvents.emit(SHAPE_CHANGE_PENDING_EVENT_HANDLED);
    loadEmit(false);
    loadProgressEmit(-1);
    (window as any)?.electronCenter?.emit(CMEVENTS.MODELS_RENDERER_END);
  });
};

/**
 *
 * @param dxids
 * @param force  如果dxids=[]空,则获取缓存内容,如果dxids不是空的,則重新生成三角化数据;
 *               如果dxids不为空且希望获取缓存则设置force=false;
 */
async function modelTask(
  dxids: string[],
  { force = false, checkVisibility = true }: ModelTaskOptions,
) {
  try {
    const renderer = RendererMgr.getRender<Renderer3D>('3d');

    const modelsData = await CoreBasicCommands.getVisualData(
      dxids,
      ExtensionID.CAD,
      checkVisibility,
      force,
    );
    ctx2.viewer.sceneSetup.adjustCameraForVisualData(modelsData);

    if (modelsData?.length > 0) {
      renderer.saveShapeDatas(modelsData);
      await renderer.drawNewModels(dxids);
    } else {
      // for a sketcher with no curves left
      const picker = PickerMgr.getPicker<Picker3D>('3d');
      picker.deleteModels(dxids, true);
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }

    dxids.forEach((dxid) => {
      const obj = docMgr.getDocumentObjectById(dxid);
      if (dxid !== docMgr.getCurrentSketchDxid()) {
        if (obj?.type === Types.Sketcher) {
          docMgr.loadSketchChildren(dxid);
        }
      }

      if (!obj.isRecursiveVisible) {
        renderer.setModelVisible(dxid, false);
      }
    });
  } catch {
    // ignore
  }
}
// 当前版本获取装饰（螺纹），后续螺纹 nextCore 改进后，需更换渲染逻辑
export async function renderThread(event: ShapeChangePendingEvent = null) {
  const {
    added = [],
    removed = [],
    shapeChanged = [],
    placementChanged = [],
    propertyChanged = [],
    visibilityChanged = [],
  } = event || {};

  const renderer = RendererMgr.getRender<Renderer3D>('3d');
  const picker = PickerMgr.getPicker<Picker3D>('3d');

  const objectInfoMap = dataManager.getObjectsInfoMap();
  const threadObjects = Array.from(objectInfoMap.values()).filter(
    (object) => object.thread,
  );
  let threadDxids = threadObjects.map((object) => object.thread.id);

  // 过滤受影响的螺纹对象
  const affectedArrays = [
    added,
    removed,
    shapeChanged,
    placementChanged,
    propertyChanged,
    visibilityChanged,
  ];
  threadDxids = threadDxids.filter((dxid) =>
    affectedArrays.some((array) => array.length > 0 && array.includes(dxid)),
  );

  if (event) {
    if (threadDxids.length <= 0) {
      return;
    }
    // 清除螺纹
    picker.deleteModels(threadDxids, true);
    threadDxids.forEach((dxid) => renderer.deleteModelByDxid(dxid));
  }

  // 渲染螺纹
  const finalThreadObjects = event
    ? threadObjects.filter((object) => threadDxids.includes(object?.thread?.id))
    : threadObjects;
  for (const object of finalThreadObjects) {
    try {
      const threadRenderData = await CoreBasicCommands.getThreadRenderData(
        object?.thread?.id,
        object?.thread?.face,
      );
      threadRenderData.id = object?.thread?.id;
      // 直接使用 renderer.renderModels([threadRenderData]); 会导致无限平面之后无法拾取
      renderer.saveShapeDatas([threadRenderData]);
      await renderer.drawNewModels([object?.thread?.id]);
    } catch (error) {
      console.error(error);
    }
  }
}
